#include "config.h"

#include "lib/libedb/edb.h"

#define ECM_MAX_COUNT	4

static ECMBOOL just_start_ECM;
static ECMBOOL just_stop_ECM;
static ECMBOOL no_start_ECM;

ecm_uint16	port = 10001;
ecm_uint16	egid_port = 10080;
static ecm_uint32	ECM_count = 1;

char	*runtest_home;

extern void build_clxhome(const char *clxdir);

static void
usage(void)
{
	printf(
"Usage: runtest <options>\n"
" Options:\n"
"  -h: help\n"
"  -x: no start ECM daemons\n"
"  -s: just start ECM daemons\n"
"  -k: just kill ECM daemons\n"
"  -p <ebc port>: ebc port base\n"
"  -P <egid port>: egid port base\n"
"  -N <ECM instance count>\n"
	);
}

static void
show_log(const char *logpath)
{
	FILE	*fp;
	char	buf[1024];

	ecm_test_verbose("\nerror log:\n");
	ecm_test_verbose("--------------------------------------\n");
	fp = ecm_fopen(logpath, "r");
	if (fp) {
		while (ecm_fgets(buf, 1024, fp)) {
			ecm_test_verbose("%s", buf);
		}
		ecm_fclose(fp);
	}
	ecm_test_verbose("--------------------------------------\n");
}

#define INBUF_SIZE	4096

static char *
read_output(int fd)
{
	char	*outfile;
	int	fd_out;
	char	buf[INBUF_SIZE];

	if ((fd_out = ecm_tmpfile(&outfile)) < 0)
		return NULL;
	while (ECM_TRUE) {
		ecm_size_t	size = INBUF_SIZE;

		if (!ecm_read(fd, buf, &size) || size == 0)
			break;
		ecm_write(fd_out, buf, &size);
	}
	ecm_close(fd_out);
	return outfile;
}

static ECMBOOL
run_command(const char *clxhome, const char *command, int *pexitcode, char **poutfile)
{
	ecm_exectx_t	ctx;
	ecm_err_t	err;
	const char	*env_arr[] = { NULL, NULL };
	int	fd_in = -1;
	char	*outfile = NULL;
	char	*env;

	ecm_asprintf(&env, "CLX_HOME=%s", clxhome);
	ecm_init_exectx(&ctx);
	ctx.command = command;
	env_arr[0] = env;
	ctx.env_arr = env_arr;
	ctx.stderr_enabled = ECM_TRUE;

	if ((err = ecm_exec_process(&ctx, &fd_in, NULL)) == ECM_OK) {
		outfile = read_output(fd_in);
		if ((err = ecm_wait_process(&ctx, -1)) == ECM_OK)
			*pexitcode = ctx.exitcode;
	}

	ecm_close(fd_in);

	ecm_free(env);

	ecm_clear_exectx(&ctx);

	if (err != ECM_OK) {
		if (outfile)
			ecm_unlink(outfile);
		if (poutfile)
			*poutfile = NULL;
		return ECM_FALSE;
	}

	if (poutfile)
		*poutfile = outfile;
	else
		ecm_unlink(outfile);
	return ECM_TRUE;
}

static ECMBOOL
start_ECM_daemons(const char *clxhome, char **plogpath)
{
	char	*ecmconf_path;
	char	*command;
	int	exitcode;

	ecmconf_path = ecm_gen_rpath(clxhome, "bin/ecmconf");
	ecm_asprintf(&command, "\"%s\" install", ecmconf_path);
	ecm_free(ecmconf_path);

	if (!run_command(clxhome, command, &exitcode, plogpath)) {
		ecm_free(command);
		return ECM_FALSE;
	}
	ecm_free(command);

	if (exitcode != 0)
		return ECM_FALSE;

	if (plogpath) {
		ecm_unlink(*plogpath);
		ecm_free(*plogpath);
		*plogpath = NULL;
	}
	return ECM_TRUE;
}

static ECMBOOL
stop_ECM_daemons(const char *clxhome, char **plogpath)
{
	char	*ecmconf_path;
	char	*command;
	int	exitcode;

	ecmconf_path = ecm_gen_rpath(clxhome, "bin/ecmconf");
	ecm_asprintf(&command, "\"%s\" remove", ecmconf_path);
	ecm_free(ecmconf_path);

	if (!run_command(clxhome, command, &exitcode, plogpath)) {
		ecm_free(command);
		return ECM_FALSE;
	}
	ecm_free(command);

	if (exitcode != 0)
		return ECM_FALSE;
	if (plogpath)
		ecm_unlink(*plogpath);
	return ECM_TRUE;
}

static void
setup_econf(const char *clxhome, ecm_uint32 idx, ECMBOOL as_master)
{
	char	*ecmconf_path;
	char	*command;
	int	exitcode;

	ecmconf_path = ecm_gen_rpath(clxhome, "bin/ecmconf");
	ecm_asprintf(&command, "\"%s\" -c test_cluster -m %u -n nd%u -p %hu -e %hu "
		     "-A \"ebcd=-D -v -l . -H \"\"%s\"\"\" "
		     "-A \"edbd=-D -v -l . -H \"\"%s\"\"\" "
		     "-A \"egid=-D -H \"\"%s\"\"\" "
		     "-S ebcd=ebcd%u "
		     "-S edbd=edbd%u "
		     "-S egid=egid%u update",
		     ecmconf_path,
		     as_master ? 1: 0, idx,
		     port + (ecm_uint16)idx, egid_port + (ecm_uint16)idx,
		     clxhome, clxhome, clxhome, idx, idx, idx);

	if (!run_command(clxhome, command, &exitcode, NULL))
		ecm_test_failed("cannot set econf\n");

	ecm_free(command);

	if (exitcode != 0)
		ecm_test_failed("cannot set econf: %d\n", exitcode);

	if (ECM_count > 1 && idx == 0) {
		ecm_uint32	i;

		for (i = 1; i < ECM_count; i++) {
			ecm_asprintf(&command, "\"%s\" -r nd%u,sock/tcp/127.0.0.1:%u update",
				     ecmconf_path, i,
				     port + (ecm_uint16)i);
			if (!run_command(clxhome, command, &exitcode, NULL))
				ecm_test_failed("cannot set econf\n");

			ecm_free(command);

			if (exitcode != 0)
				ecm_test_failed("cannot set econf: %d\n", exitcode);
		}
	}

	ecm_free(ecmconf_path);
}

static void
start_ECM_instance(ecm_uint32 idx, ECMBOOL as_master)
{
	char	*clxhome, *clxcnt;
	char	*logpath;

	ecm_asprintf(&clxcnt, "clx%u", idx);
	clxhome = ecm_gen_rpath(runtest_home, clxcnt);
	ecm_free(clxcnt);
	
	build_clxhome(clxhome);

	setup_econf(clxhome, idx, as_master);

	if (start_ECM_daemons(clxhome, &logpath)) {
		ecm_unlink(logpath);
	}
	else {
		show_log(logpath);
		ecm_unlink(logpath);
		ecm_test_failed("failed!!");
	}

	ecm_free(clxhome);
}

static void
stop_ECM_instance(ecm_uint32 idx)
{
	char	*logpath;
	char	*clxhome, *clxcnt;

	ecm_asprintf(&clxcnt, "clx%u", idx);
	clxhome = ecm_gen_rpath(runtest_home, clxcnt);
	ecm_free(clxcnt);

	if (stop_ECM_daemons(clxhome, &logpath))
		ecm_unlink(logpath);
	else {
		show_log(logpath);
		ecm_unlink(logpath);
		ecm_test_failed("failed!!!");
	}
	ecm_free(clxhome);
}

static void
start_ECM(void)
{
	ecm_uint32	i;
	
	ecm_test_start("starting ECM daemons");

	for (i = 0; i < ECM_count; i++) {
		start_ECM_instance(i, i == 0);
	}
	ecm_test_ok();
}

static void
stop_ECM(void)
{
	ecm_uint32	i;
	
	ecm_test_start("stopping ECM daemons");

	for (i = 0; i < ECM_count; i++) {
		stop_ECM_instance(i);
	}
	ecm_test_ok();
}

static void
run_driver(const char *clxhome, const char *drivers_path, const char *driver_name)
{
	char	*driver_path;
	int	exitcode;
	char	*logpath;

	ecm_test_start("running test driver: %s", driver_name);

	driver_path = ecm_gen_rpath(drivers_path, driver_name);
	if (!run_command(clxhome, driver_path, &exitcode, &logpath)) {
		ecm_test_failed("cannot run driver");
	}
	ecm_free(driver_path);

	if (exitcode != 0) {
		show_log(logpath);
		ecm_unlink(logpath);
		ecm_test_failed("exit code: %d\n", exitcode);
	}
	ecm_test_ok();
}

static void
run_drivers(void)
{
	char	*drivers_path;
	char		*clxhome;
	const char	*name;
	ecm_dir_t	dir;

	clxhome = ecm_gen_rpath(runtest_home, "clx0");

	drivers_path = ecm_gen_rpath(runtest_home, "drivers");
	dir = ecm_opendir(drivers_path);
	if (dir == NULL)
		ecm_test_failed("%s: drivers path does not exist", drivers_path);

	while ((name = ecm_readdir(dir))) {
		if (name[0] == '.')
			continue;
		run_driver(clxhome, drivers_path, name);
	}

	ecm_free(clxhome);
	ecm_free(drivers_path);
	ecm_closedir(dir);
}

static void
parse_args(int argc, char *argv[])
{
	int	c;

	while ((c = ecm_getopt(argc, argv, "hxskn:p:P:N:")) != -1) {
		switch(c) {
		case 'h':
			usage();
			ecm_exit(0);
			break;
		case 'x':
			no_start_ECM = ECM_TRUE;
			break;
		case 's':
			just_start_ECM = ECM_TRUE;
			break;
		case 'k':
			just_stop_ECM = ECM_TRUE;
			break;
		case 'p':
			ecm_sscanf(ecm_optarg, "%hu", &port);
			break;
		case 'P':
			ecm_sscanf(ecm_optarg, "%hu", &egid_port);
			break;
		case 'N':
			ecm_sscanf(ecm_optarg, "%u", &ECM_count);
			if (ECM_count > ECM_MAX_COUNT) {
				ecm_fprintf(ecm_stderr, "Too many ECM instance count\n");
				ecm_exit(1);
			}
			break;
		default:
			usage();
			ecm_exit(1);
			break;
		}
	}
}

int
main(int argc, char *argv[])
{
	char	*progname;

	edb_initialize();

	progname = ecm_get_progname();
	runtest_home = ecm_dirname(progname);

	parse_args(argc, argv);

	if (just_start_ECM) {
		start_ECM();
		return 0;
	}
	if (just_stop_ECM) {
		stop_ECM();
		return 0;
	}

	if (!no_start_ECM)
		start_ECM();

	run_drivers();

	if (!no_start_ECM)
		stop_ECM();

	return 0;
}
